package ru.ifmo.sound.neuro;

import ru.ifmo.sound.VectorGenerator;

import java.util.ArrayList;
import java.util.List;

/**
 * @author avhaliullin
 */
public final class NeuronetFactory {
    private NeuronetFactory() {
    }

    private static final NeuronetFactory INSTANCE = new NeuronetFactory();

    public static NeuroNet create(int classesCount) {
        return new NeuroNet(VectorGenerator.getHigherSpectrumSize() * VectorGenerator.getLowerSpectrumSize(), 0.1,
                INSTANCE.new GroupingLayerBuilder(VectorGenerator.getHigherSpectrumSize(), VectorGenerator.getHigherSpectrumSize()),
                INSTANCE.new GroupingLayerBuilder(VectorGenerator.getHigherSpectrumSize(), classesCount),
//                INSTANCE.new GroupingLayerBuilder(classesCount, classesCount),
//                INSTANCE.new GroupingLayerBuilder(classesCount, classesCount),
                INSTANCE.new MergingLayerBuilder(classesCount));
    }

    public static NeuroNet createSimple(int inputs, int layersCount, double educationSpeed) {
        LayerBuilder[] builders = new LayerBuilder[layersCount];
        for (int i = 0; i < layersCount; i++) {
            builders[i] = createAllToAllLayer(inputs);
        }
        return new NeuroNet(inputs, educationSpeed, builders);
    }

    public static LayerBuilder createMergingLayer(int groupSize) {
        return INSTANCE.new MergingLayerBuilder(groupSize);
    }

    private static LayerBuilder createAllToAllLayer(int layerSize) {
        return INSTANCE.new AllToAllLayerBuilder(layerSize);
    }

    private List<Neuron> getNeurons(int count) {
        List<Neuron> res = new ArrayList<Neuron>();
        for (int i = 0; i < count; i++) {
            res.add(new Neuron());
        }
        return res;
    }

    class AllToAllLayerBuilder implements LayerBuilder {
        private final int layerSize;

        public AllToAllLayerBuilder(int layerSize) {
            this.layerSize = layerSize;
        }

        public List<Neuron> build(List<Neuron> prev) {
            List<Neuron> res = getNeurons(layerSize);
            for (Neuron newNeuron : res) {
                for (Neuron prevNeuron : prev) {
                    newNeuron.prevLayer.add(newNeuron.new WeightedNeuron(prevNeuron, VectorGenerator.randomWeight()));
                }
            }
            return res;
        }
    }

    class GroupingLayerBuilder implements LayerBuilder {
        private final int groupSize;
        private final int groupValues;

        public GroupingLayerBuilder(int groupSize, int groupValues) {
            this.groupSize = groupSize;
            this.groupValues = groupValues;
        }

        public List<Neuron> build(List<Neuron> prev) {
            if (prev.size() % groupSize != 0) {
                throw new AssertionError("Invalid group size " + groupSize + " for input " + prev.size());
            }

            List<Neuron> res = new ArrayList<Neuron>();
            List<Neuron> group = getNeurons(groupValues);
            for (int i = 0; i < prev.size(); i++) {

                for (Neuron groupNeuron : group) {
                    groupNeuron.prevLayer.add(groupNeuron.new WeightedNeuron(prev.get(i), VectorGenerator.randomWeight()));
                }
                if ((i + 1) % groupSize == 0) {
                    res.addAll(group);
                    group = getNeurons(groupValues);
                }
            }
            return res;
        }
    }

    class MergingLayerBuilder implements LayerBuilder {
        private final int groupSize;

        public MergingLayerBuilder(int groupSize) {
            this.groupSize = groupSize;
        }

        public List<Neuron> build(List<Neuron> prev) {
            if (prev.size() % groupSize != 0) {
                throw new AssertionError("Invalid group size " + groupSize + " for input " + prev.size());
            }

            List<Neuron> res = getNeurons(groupSize);
            for (int i = 0; i < prev.size(); i++) {
                int group = i % groupSize;
                res.get(group).prevLayer.add(res.get(group).new WeightedNeuron(prev.get(i), VectorGenerator.randomWeight()));
            }
            return res;
        }
    }


}
